﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace tactics
{
    using Coord = Utility.Pair<int, int>;
    using MoveMatrix = Dictionary<int, int>;
    using MatrixMap = Dictionary<int, Dictionary<int, int>>;

    public class UnitMoveMatrices
    {
        MatrixMap moveMatrices;
        int playerNum;
        MoveMatrix unitsCurrentPath;

        #region Properties

        public MoveMatrix UnitsMovePath
        {
            get
            {
                return unitsCurrentPath;
            }
        }

        public MatrixMap MoveMatrices
        {
            get
            {
                return moveMatrices;
            }
        }

        #endregion

        public UnitMoveMatrices(Player p, TerrainMap map, MovementRules moveRules)
        {
            moveMatrices = new MatrixMap();
            unitsCurrentPath = new MoveMatrix();

            GenerateMatrices(p, map, moveRules);
        }

        private void GenerateMatrices(Player p, TerrainMap map, MovementRules moveRules)
        {
            playerNum = p.Number;

            foreach (Unit u in p.Units)
            {
                moveMatrices[u.Id] = new MoveMatrix();

                if (!u.Moved)
                {
                    MoveMatrix set = new MoveMatrix();

                    PathR(map, moveRules, u, u.CurrentSpeed, new Coord(u.X, u.Y), new Coord(-1, -1), set);

                    // remove units current location from the move matrix
                    set.Remove(map.AtCoord(u.X, u.Y));

                    // remove blocked terrain areas from movement map
                    // flying/burrowed units can cross some of these terrains -- but they can't stop on them
                    var invalid = (from idx in set.Keys
                                  where map.Map[idx] == TerrainRule.Blocked ||
                                        map.Map[idx] == TerrainRule.Wall    ||
                                        map.Map[idx] == TerrainRule.Impassable
                                   select idx).ToList();

                    foreach (var key in invalid)
                        set.Remove(key);

                    moveMatrices[u.Id] = set;
                }
            }
        }

        private void PathR(TerrainMap map, MovementRules rules, Unit u, int movesLeft, Coord coord, Coord last, MoveMatrix set)
        {
            // base cases:
            //   if we're off the map
            //   if we're out of moves
            //   if unit can't move here

            // set this tiles value
            set[map.AtCoord(coord.First, coord.Second)] = movesLeft;

            if (movesLeft > 0)
            {
                bool recurse;
                int nextMovesLeft;
                int idx;

                for (int y = coord.Second - 1; y <= coord.Second + 1; ++y)
                {
                    for (int x = coord.First - 1; x <= coord.First + 1; ++x)
                    {
                        // don't look back on ourselves
                        // make sure we're in bounds
                        if (!(x == last.First && y == last.Second) &&
                            ((x >= 0 && y >= 0) && (x < map.Width && y < map.Height)))
                        {
                            recurse = true;
                            nextMovesLeft = -1;

                            idx = map.AtCoord(x, y);
                            
                            MovementType moveType = rules.GetMovementTypeByName(u.MovementType);
                            string ruleType = map.GetTypeAt(x, y).ToString();

                            if (!moveType.Costs.ContainsKey(ruleType))
                                recurse = false;
                            else
                            {
                                int playerAtXY = map.GetPlayerAt(x, y);

                                // this square is occupied by an enemy unit
                                if (playerAtXY != -1 && playerAtXY != playerNum && !moveType.Costs.ContainsKey("EnemyUnits"))
                                    recurse = false;
                                else
                                    nextMovesLeft = movesLeft - moveType.Costs[ruleType];
                            }

                            // this move is too expensive, back out
                            if (nextMovesLeft < 0)
                                recurse = false;

                            // this move is less efficient than some other way to reach this tile, back out
                            if (set.ContainsKey(idx) && (set[idx] >= nextMovesLeft))
                                recurse = false;

                            if (recurse)
                                PathR(map, rules, u, nextMovesLeft, new Coord(x, y), new Coord(coord.First, coord.Second), set);
                        }
                    }
                }
            }
        }

        public void UpdateUnitMovePath(Unit unit, Coord location, TerrainMap terrainMap)
        {
            int goalX = location.First;
            int goalY = location.Second;
            int idx = terrainMap.AtCoord(goalX, goalY); 
            bool done = false;
            int counter = 0;
            MoveMatrix unitsMoveMap = moveMatrices[unit.Id];
            unitsCurrentPath = new Dictionary<int, int>();

            // draw the path from current highlight to selected unit
            while (!done && unitsMoveMap.Keys.Contains(idx))
            {
                unitsCurrentPath[idx] = counter++;

                int high = idx;
                int curr = 0;
                float slope = 0;
                int nextX = -1;
                int nextY = -1;

                for (int y = goalY - 1; y <= goalY + 1; ++y)
                {
                    for (int x = goalX - 1; x <= goalX + 1; ++x)
                    {
                        curr = terrainMap.AtCoord(x, y);

                        if (unitsMoveMap.Keys.Contains(curr))
                        {
                            if (unitsMoveMap[curr] > unitsMoveMap[high])
                            {
                                high = curr;

                                slope = Utility.Slope.Difference(x, y, goalX, goalY, unit.X, unit.Y);

                                nextX = x;
                                nextY = y;
                            }
                            else if (unitsMoveMap[curr] == unitsMoveMap[high])
                            {
                                float slopeCheck = Utility.Slope.Difference(x, y, goalX, goalY, unit.X, unit.Y);

                                if (slopeCheck <= slope)
                                {
                                    high = curr;
                                    slope = slopeCheck;
                                    nextX = x;
                                    nextY = y;
                                }
                            }
                        }
                    }
                }

                if (high == idx)
                    done = true;
                else
                {
                    idx = high;
                    goalX = nextX;
                    goalY = nextY;
                }
            }
        }

        #region Accessors

        public MoveMatrix GetMatrixById(int id)
        {
            if (moveMatrices.ContainsKey(id))
                return moveMatrices[id];
            else
                return null;
        }

        #endregion
    }
}
